package org.codetranslators.compiler.tiger.semantics;

import java.util.Stack;
import java.util.Vector;

import org.codetranslators.common.Label;
import org.codetranslators.common.Temp;
import org.codetranslators.compiler.abstractassembly.AssemType;
import org.codetranslators.compiler.mips.translate.ActivationFrame;
import org.codetranslators.compiler.tiger.abssyntree.*;
import org.codetranslators.compiler.tiger.ErrorHandler;

public class SemanticAnalyzer {

	private Environments environments;
	private ErrorHandler errorHandler;
	
	TranslatingMediator translatingMediator;
	
	Stack loopLabelStack;  // To keep track of labels for break statements
	
	private boolean loopBeingAnalyzed; // This flag will be used while analyzing the break expression
	
	private boolean semanticAnalyzerError;
	
	private Level currentFunctionLevel;     // The current level of the function being analyzed;
	private Level mainFunctionLevel;        // The level of the "main" function
	
	private Vector functionLevels;    // The collection of function levels that will returned 
									  // after analysis
		
	public SemanticAnalyzer()
	{
		functionLevels = new Vector();
		
		mainFunctionLevel = new Level(null, Symbol.symbol("main"));
		currentFunctionLevel = mainFunctionLevel;
		
		environments = new Environments(currentFunctionLevel);
		loopBeingAnalyzed = false;
		
		errorHandler = ErrorHandler.getInstance();
		
		loopLabelStack = new Stack();
		
		translatingMediator = new TranslatingMediator();
		
		semanticAnalyzerError = false;
	}
	
	public Vector translateAbstractTree(Absyn abstractSyntaxTree)
	{
		Exp expression = (Exp) abstractSyntaxTree;
		TranslatedExpAndType translatedExpAndType =  translateExpression(expression);
		
		mainFunctionLevel.setFunctionBody(translatedExpAndType.getAssemType());
		functionLevels.addElement(mainFunctionLevel);
		
		return functionLevels;
	}
	
	public TranslatedExpAndType translateExpression(Exp expression)
	{	
		if(expression instanceof ArrayExp)
		{
			// The expression is an assignment expression
			ArrayExp arrayExpression = (ArrayExp)expression;
			return translateArrayExp(arrayExpression);	
		}
		if(expression instanceof AssignExp)
		{
			// The expression is an assignment expression
			AssignExp assignExpression = (AssignExp)expression;
			return translateAssignExp(assignExpression);	
		}
		
		else if(expression instanceof BreakExp)
		{
			// The expression is a break expression
			BreakExp breakExpression = (BreakExp)expression;
			return translateBreakExp(breakExpression);	
		}
		
		else if(expression instanceof CallExp)
		{
			// The expression is a call expression
			CallExp callExpression = (CallExp)expression;
			return translateCallExp(callExpression);	
		}
		
		else if(expression instanceof ForExp)
		{
			// The expression is a for expression
			ForExp forExpression = (ForExp)expression;
			return translateForExp(forExpression);	
		}
		
		else if(expression instanceof IfExp)
		{
			// The expression is an if expression
			IfExp ifExpression = (IfExp)expression;
			return translateIfExp(ifExpression);	
		}
		
		else if(expression instanceof IntExp)
		{
			// The expression is an int expression
			IntExp intExpression = (IntExp)expression;
			return translateIntExp(intExpression);	
		}
		
		else if(expression instanceof LetExp)
		{
			// The expression is a let expression
			LetExp letExpression = (LetExp)expression;
			return translateLetExp(letExpression);	
		}
		
		else if(expression instanceof NilExp)
		{
			// The expression is a nil expression
			NilExp nilExpression = (NilExp)expression;
			return translateNilExp(nilExpression);	
		}
		
		else if(expression instanceof OpExp)
		{
			// The expression is an operator expression
			OpExp opExpression = (OpExp)expression;
			return translateOpExp(opExpression);	
		}
		
		else if(expression instanceof RecordExp)
		{
			// The expression is a record expression
			RecordExp recordExpression = (RecordExp)expression;
			return translateRecordExp(recordExpression);	
		}
		
		else if(expression instanceof SeqExp)
		{
			// The expression is a sequential expression
			SeqExp seqExpression = (SeqExp)expression;
			return translateSeqExp(seqExpression);	
		}
		
		else if(expression instanceof StringExp)
		{
			// The expression is a string expression
			StringExp strExpression = (StringExp)expression;
			return translateStringExp(strExpression);	
		}
		
		else if(expression instanceof VarExp)
		{
			// The expression is a variable expression
			VarExp varExpression = (VarExp)expression;
			Var var = varExpression.getVar();
			return translateVarExp(var);	
		}
		
		else if(expression instanceof WhileExp)
		{
			// The expression is a while expression
			WhileExp whileExpression = (WhileExp)expression;
			return translateWhileExp(whileExpression);	
		}
		else
			return null;
	}
	
	public TranslatedExpAndType translateArrayExp(ArrayExp arrayExpression)
	{
		Symbol typeSymbol = arrayExpression.getTypeSymbol();
		TypeEntry baseTpEntry = (TypeEntry)environments.getTypeTable().get(typeSymbol);
		
		ArrayTypeEntry arrTpEntry = null;
		if(baseTpEntry == null)
		{
			errorHandler.addError(arrayExpression.getPosition(), ErrorHandler.THE_TYPE, 
					typeSymbol.toString(), ErrorHandler.NOT_DEFINED);
		}
		else
		{
			if(baseTpEntry instanceof ArrayTypeEntry)
			{
				arrTpEntry = (ArrayTypeEntry) baseTpEntry;
				Exp initValue = arrayExpression.getInitValue();
	
				if(!(translateExpression(initValue).getTypeEntry() == arrTpEntry.getBaseTypeEntry()))
				{
					errorHandler.addError(arrayExpression.getPosition(), ErrorHandler.ARRAY_INIT_VALUE_MISMATCH);
				}
			}
			else
			{
				errorHandler.addError(arrayExpression.getPosition(), ErrorHandler.THE_TYPE, 
						typeSymbol.toString(), ErrorHandler.NOT_ARRAY);
			}
		}
		
		Exp sizeExp = arrayExpression.getSize();
		if(!(translateExpression(sizeExp).getTypeEntry() instanceof IntTypeEntry))
		{
			errorHandler.addError(arrayExpression.getPosition(), ErrorHandler.ARRAY_SIZE_NOT_INT);
		}
		
		TranslatedExpAndType retTrans = new TranslatedExpAndType();
		retTrans.setEntry(baseTpEntry); 
		return retTrans; 
		
	}
	
	public TranslatedExpAndType translateAssignExp(AssignExp assignExpression)
	{
		Var lhs = assignExpression.getLValue();
		Exp rhs = assignExpression.getRhs();
		
		TranslatedExpAndType transExpAndTypeLHS = translateVarExp(lhs);
		TypeEntry varTypeEntry = transExpAndTypeLHS.getTypeEntry();
		
		TranslatedExpAndType transExpAndTypeRHS = translateExpression(rhs);
		TypeEntry rhsTpEntry  = transExpAndTypeRHS.getTypeEntry();
		if(varTypeEntry != rhsTpEntry)
		{
			if(!(varTypeEntry instanceof RecordTypeEntry && rhsTpEntry instanceof NilTypeEntry))
			{
				semanticAnalyzerError = true;
				errorHandler.addError(lhs.getPosition(), ErrorHandler.TP_MISMATCH_ASSGN);
			}
		}
		else 
		{
			// Both cannot be nil
			if(varTypeEntry instanceof NilTypeEntry)
			{
				semanticAnalyzerError = true;
				errorHandler.addError(lhs.getPosition(), ErrorHandler.TP_MISMATCH_ASSGN);
			}
		}
		
		TranslatedExpAndType retTrans = new TranslatedExpAndType();
		if(!semanticAnalyzerError)
		{
			// No error during semantic processing - translate it to the intermediate representation
			AssemType assemAssign = translatingMediator.translateAssignStm(transExpAndTypeLHS.getAssemType(),
					transExpAndTypeRHS.getAssemType());
			retTrans.setAssemType(assemAssign);	
		}
		
		retTrans.setEntry(VoidTypeEntry.getInstance()); // Assign expression does not evaluate to any value
		return retTrans;
	}
	
	public TranslatedExpAndType translateBreakExp(BreakExp breakExpression)
	{
		if(!loopBeingAnalyzed)
		{
			semanticAnalyzerError = true;
			errorHandler.addError(breakExpression.getPosition(), ErrorHandler.BREAK_NOT_IN_LOOP);
		}
		
		TranslatedExpAndType retTrans = new TranslatedExpAndType();
		if(!semanticAnalyzerError)
		{
			Label endLoopLabel = (Label) loopLabelStack.pop();
			AssemType assemBreak = translatingMediator.translateBreakStm(endLoopLabel);
			retTrans.setAssemType(assemBreak);
		}
		retTrans.setEntry(VoidTypeEntry.getInstance()); // The break expression does not evaluate to any value
		return retTrans;
	}
	
	public TranslatedExpAndType translateCallExp(CallExp callExp)
	{
		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		
		Symbol funcSymbol = callExp.getFuncName();
		
		Entry entr = (Entry) environments.getVariableTable().get(funcSymbol);
		FunctionEntry fEntry = null;
		int paramCount = 0;
		
		RecordTypeEntry fParamsEntry = null;
		TypeEntry retTypeEntry = null;
		
		Stack argsStack = null;
		Label fLabel = null;
		
		if(entr == null)
		{
			errorHandler.addError(callExp.getPosition(), ErrorHandler.THE_FUNCTION, 
					funcSymbol.toString(), ErrorHandler.NOT_DEFINED);
		}
		else
		{
			if(entr instanceof FunctionEntry)
			{
				fEntry = (FunctionEntry) entr;
				// Add this to the called functions list of the current level
				currentFunctionLevel.addCalledFunction(fEntry.getLevel());

				//fLabel = fEntry.getLevel().getActivationFrame().getLabel();
				//fLabel = new Label(fEntry.getLevel().getLevelName().toString());
				fLabel = new Label(funcSymbol.toString());
				retTypeEntry = fEntry.getReturnType();
				fParamsEntry = fEntry.getFormals();
				
				translatedExpAndType.setEntry(retTypeEntry);
				
				RecordTypeEntry re = fParamsEntry;
				
				int count = 1;
				argsStack = new Stack();
				for(ParamExpList paramExpList = callExp.getParamList(); 
						paramExpList != null; paramExpList = paramExpList.getExpList())
				{
					paramCount++;
					Exp exp = paramExpList.getHead();
					if(re != null)
					{
						TranslatedExpAndType tte = translateExpression(exp);
						AssemType assemTypeExp = tte.getAssemType();
						argsStack.push(assemTypeExp);
						if(tte.getTypeEntry() != re.getFieldType())
						{
							errorHandler.addError(callExp.getPosition(), ErrorHandler.THE_ARGUMENT_NUM, "" + count, 
									ErrorHandler.PARAM_MISMATCH);
						}
						re = re.getNext();
					}
					else
					{
						errorHandler.addError(callExp.getPosition(), ErrorHandler.NUM_ARGS_MISMATCH);
					}
					count++;
				}
				
				if(re != null)
				{
					// Function definition has more parameters than the call
					errorHandler.addError(callExp.getPosition(), ErrorHandler.NUM_ARGS_MISMATCH);
				}
			}
			else
			{
				errorHandler.addError(callExp.getPosition(), ErrorHandler.THE_FUNCTION, 
						funcSymbol.toString(), ErrorHandler.NOT_DEFINED);
			}
		}

		// Translate the call to the abstract assembly type
		if(!(argsStack == null || fLabel == null))
		{
			boolean hasReturnValue = true;
			if(retTypeEntry instanceof VoidTypeEntry) hasReturnValue = false;
			
			
			//TODO - Get the correct value of maxOutgoingParams
			int maxOutgoingParams = 3;
			
			
			AssemType retAssemType = translatingMediator.translateCallExp(fLabel, argsStack, paramCount,
										hasReturnValue, maxOutgoingParams, ActivationFrame.WORDSIZE);
			translatedExpAndType.setAssemType(retAssemType);
		}
		return translatedExpAndType;
	}
	
	public TranslatedExpAndType translateForExp(ForExp forExpression)
	{
		/*
		loopBeingAnalyzed = true;
		
		VarDec forVarDec = forExpression.getVarDec();
		Exp highExp = forExpression.getHigh();
		Exp forBody = forExpression.getBody();
		
		environments.getVariableTable().beginScope();
		TranslatedExpAndType expAndType = translateVarDec(forVarDec);
		VariableEntry varEntry = ;
		
		// The variable must be an int type
		if(!(varEntry.getType() instanceof IntTypeEntry))
		{
			errorHandler.addError(forVarDec.getPosition(), ErrorHandler.THE_VARIABLE, 
					forVarDec.getName().toString(), ErrorHandler.MUST_BE_INT);
		}
		
		TranslatedExpAndType exp2 = translateExpression(highExp);
		if(!(exp2.getTypeEntry() instanceof IntTypeEntry))
		{
			errorHandler.addError(forVarDec.getPosition(), ErrorHandler.FORLOOP_HIVALUE_MUST_BE_INT);
		}
		
		// The body of the for loop must not evaluate any value
		TranslatedExpAndType exp3 = translateExpression(forBody);
		if(exp3.getTypeEntry() != null)
		{
			errorHandler.addError(forVarDec.getPosition(), ErrorHandler.FORLOOP_BODY_NULL);
		}
		
		environments.getVariableTable().endScope();
		
		loopBeingAnalyzed = false;
		
		TranslatedExpAndType retTrans = new TranslatedExpAndType();
		retTrans.setEntry(null); // For expression does not evaluate to any type
		return retTrans;
		*/
		return null;
	}
	
	public TranslatedExpAndType translateIfExp(IfExp ifExpression)
	{
		Exp test = ifExpression.getTest();
		Exp thenClause = ifExpression.getThenClause();
		Exp elseClause = ifExpression.getElseClause();
		
		TranslatedExpAndType transExpAndTypeThen = translateExpression(thenClause);
		
		TypeEntry ifExprType = transExpAndTypeThen.getTypeEntry();
		
		TranslatedExpAndType transExpAndTypeElse = null;
		
		TypeEntry elseExprType = null;
		if(elseClause != null)
		{
			transExpAndTypeElse = translateExpression(elseClause);
			elseExprType = transExpAndTypeElse.getTypeEntry();
		}
		
		TranslatedExpAndType transTest = translateExpression(test);
		if(!(transTest.getTypeEntry() instanceof IntTypeEntry))
		{
			semanticAnalyzerError = true;
			errorHandler.addError(test.getPosition(), ErrorHandler.IF_TEST_EXPR
									+ ErrorHandler.MUST_BE_INT.trim());
		}
		
		if(elseClause != null)
		{
			if(ifExprType != elseExprType)
			{
				semanticAnalyzerError = true;
				errorHandler.addError(test.getPosition(), ErrorHandler.IF_THEN_ELSE_TYPE_INCOMPATIBLE);
			}
		}
		
		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		if(!semanticAnalyzerError)
		{
			// No error during semantic processing - translate it to the intermediate representation
			
			AssemType assemTypeIf = transExpAndTypeThen.getAssemType();
			AssemType assemTypeTest = transTest.getAssemType();
			AssemType assemTypeElse = null;
			if(transExpAndTypeElse != null)  // There is an else clause
				assemTypeElse = transExpAndTypeElse.getAssemType();	
			AssemType assemIf = translatingMediator.translateIfStm(assemTypeTest, assemTypeIf, assemTypeElse);
			translatedExpAndType.setAssemType(assemIf);
		}
		translatedExpAndType.setEntry(ifExprType);
		return translatedExpAndType;
	}
	
	public TranslatedExpAndType translateIntExp(IntExp intExpression)
	{
		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		int val = intExpression.getValue();
		translatedExpAndType.setAssemType(translatingMediator.translateIntLiteral(val));
		translatedExpAndType.setEntry(IntTypeEntry.getInstance());
		return translatedExpAndType;
	}
	
	public TranslatedExpAndType translateLetExp(LetExp letExpression)
	{
		// Start a new scope since this is a let expression
		environments.getTypeTable().beginScope();
		environments.getVariableTable().beginScope();
		
		// Get the declaration list and the expression list in the let expression
		DecList decList = letExpression.getDecList();
		ExpList expList = letExpression.getExpList();
		
		// Translate the declaration list
		Stack assemStack = new Stack();
		for(DecList dl = decList; dl != null; dl = dl.getNextDecList())
		{
			Dec dec = dl.getFirstDec();
			AssemType assemDec = translateDec(dec);
			if(!(assemDec == null))                      // Could be null for type and function decs,
				assemStack.push(assemDec);      // but not for var decs
		}
		
		// Translate the expression list
		TranslatedExpAndType lastTransExpAndType = null;
		for(ExpList el = expList; el != null; el = el.getNextExpList())
		{
			Exp exp = el.getFirstExp();
			lastTransExpAndType = translateExpression(exp);
			assemStack.push(lastTransExpAndType.getAssemType());
		}
		
		TranslatedExpAndType retExpAndType = new TranslatedExpAndType();
		retExpAndType.setEntry(lastTransExpAndType.getTypeEntry());
		retExpAndType.setAssemType(
				translatingMediator.translateSeqStatement(assemStack));
		
		return retExpAndType;
	}
	
	public TranslatedExpAndType translateNilExp(NilExp nilExpression)
	{
		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		translatedExpAndType.setEntry(NilTypeEntry.getInstance());
		return translatedExpAndType;
	}
	
	public TranslatedExpAndType translateOpExp(OpExp opExpression)
	{
		int operType = opExpression.getOper();
		TranslatedExpAndType transExpAndTypeLeft = translateExpression(opExpression.getLeft());
		TranslatedExpAndType transExpAndTypeRight = translateExpression(opExpression.getRight());
		
		TypeEntry tpEntryLeft = transExpAndTypeLeft.getTypeEntry();
		TypeEntry tpEntryRight = transExpAndTypeRight.getTypeEntry();
		
		TypeEntry retTypeEntry = null;
		boolean typeMismatch = false;
		
		if(operType == OpExp.PLUS || operType == OpExp.MINUS || operType == OpExp.DIV
					  || operType ==  OpExp.MUL || operType == OpExp.AND || operType == OpExp.OR)
		{
			// The left and right operators must be integers
			if(!(tpEntryLeft instanceof IntTypeEntry))
			{
				semanticAnalyzerError = true;
				typeMismatch = true;
				errorHandler.addError(opExpression.getPosition(), ErrorHandler.LEFT_OPERAND,
										opExpression.getOperator(), ErrorHandler.MUST_BE_INT);
			}
			
			if(!(tpEntryRight instanceof IntTypeEntry))
			{
				semanticAnalyzerError = true;
				typeMismatch = true;
				errorHandler.addError(opExpression.getPosition(), ErrorHandler.RIGHT_OPERAND,
						opExpression.getOperator(), ErrorHandler.MUST_BE_INT);
			}	
			if(!typeMismatch)
				retTypeEntry = IntTypeEntry.getInstance();
		}
		else
		{
			// All comparison operators now (including, equals, not equals, greater than, etc.
			if(tpEntryLeft != tpEntryRight)
			{
				// The two types are not the same, check if the left side is record and the
				// rhs is nil
				if(!(tpEntryLeft instanceof RecordTypeEntry && tpEntryRight instanceof NilTypeEntry))
				{
					semanticAnalyzerError = true;
					errorHandler.addError(opExpression.getPosition(), ErrorHandler.TP_MISMATCH_OPER);
				}
			}
			else
			{
				// The types of the rhs and lhs are the same
				if(tpEntryLeft instanceof StringTypeEntry)
				{
					// The rhs and lhs are string types, so result must be int
					retTypeEntry = IntTypeEntry.getInstance();
				}
				else if(tpEntryLeft instanceof ArrayTypeEntry || tpEntryLeft instanceof RecordTypeEntry)
				{	
					// The only operators allowed for ArrayTypeEntry and RecordTypeEntry are
					// equals and not equals
					if(!(operType == OpExp.EQ || operType == OpExp.NE))
					{
						semanticAnalyzerError = true;
						errorHandler.addError(opExpression.getPosition(), ErrorHandler.TP_MISMATCH_OPER);
					}
					else
						retTypeEntry = IntTypeEntry.getInstance();
				}
				else
					retTypeEntry = tpEntryLeft;
			}
		}
				
		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		if(!semanticAnalyzerError)
		{
			// No error during semantic processing - translate it to the intermediate representation
			AssemType assemTypeLeft = transExpAndTypeLeft.getAssemType();
			AssemType assemTypeRight = transExpAndTypeRight.getAssemType();
			translatedExpAndType.setAssemType(
					translatingMediator.translateOperatorExp(operType, assemTypeLeft, assemTypeRight));
			
		}
		translatedExpAndType.setEntry(retTypeEntry);
		return translatedExpAndType;
	}
	
	public TranslatedExpAndType translateRecordExp(RecordExp recordExpression)
	{
		Symbol typeSymbol = recordExpression.getTypeSymbol();
		FieldExpList fieldExpList = recordExpression.getFieldExpList();
		
		RecordTypeEntry recordTypeEntry = null;
		
		TypeEntry tpEntryExp = (TypeEntry) 
				environments.getTypeTable().get(typeSymbol);
		
		int fieldCount = 0;
		Stack initExprStack = new Stack();

		if(tpEntryExp == null)
		{
			errorHandler.addError(fieldExpList.getPosition(), ErrorHandler.THE_TYPE, 
					typeSymbol.toString(), ErrorHandler.NOT_DEFINED);
		}
		else
		{
			if(tpEntryExp instanceof RecordTypeEntry)
			{
				recordTypeEntry = (RecordTypeEntry) tpEntryExp;
				if(fieldExpList != null)
				{	
					for(FieldExpList fel = fieldExpList; fel != null; fel = fel.getNext())
					{
						fieldCount++;
						Symbol flName = fel.getFieldNameSymbol();
						// The above name must occur in the recordTypeEntry for this record type; check it
						
						boolean fieldFound = false;
						for(RecordTypeEntry rte = recordTypeEntry; rte != null; rte = rte.getNext())
						{
							Symbol tpEntryName = rte.getFieldName();
							TypeEntry tpEntry = rte.getFieldType();
							
							if(tpEntryName == flName)
							{
								Exp initExp = fel.getInitExp();
								TranslatedExpAndType transInitExp = translateExpression(initExp);
								AssemType assemInitExp = transInitExp.getAssemType();
								initExprStack.push(assemInitExp);
								if(!(transInitExp.getTypeEntry() == tpEntry))
								{
									errorHandler.addError(fel.getPosition(), ErrorHandler.FIELD_EXP_MISMATCH);
								}
								fieldFound = true;
								break;
							}
						}
						if(!fieldFound)
						{
							errorHandler.addError(fieldExpList.getPosition(), ErrorHandler.FIELD_NAME, 
									flName.toString(), ErrorHandler.NOT_DEFINED);
						}
					}
				}
			}
			else
			{
				errorHandler.addError(fieldExpList.getPosition(), ErrorHandler.THE_TYPE, 
						typeSymbol.toString(), ErrorHandler.NOT_RECORD);
			}
		}

		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		translatedExpAndType.setAssemType(translatingMediator.translateRecordExp(fieldCount,  initExprStack));
		translatedExpAndType.setEntry(recordTypeEntry);
		return translatedExpAndType;
	}
	
	public TranslatedExpAndType translateSeqExp(SeqExp seqExpression)
	{
		TranslatedExpAndType translatedExpAndType = null;
		
		Stack assemStack = new Stack();   // Stack to hold all the translated assembly expressions
		
		for(ExpList expList = seqExpression.getList(); expList != null; expList = expList.getNextExpList())
		{
			translatedExpAndType = translateExpression(expList.getFirstExp());
			assemStack.push(translatedExpAndType.getAssemType());
		}
		
			translatedExpAndType.setAssemType(
				translatingMediator.translateSeqStatement(assemStack));
		
		return translatedExpAndType; // The type of the seq expression is the type of the last expression
	}
	
	public TranslatedExpAndType translateStringExp(StringExp strExpression)
	{
		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		translatedExpAndType.setEntry(StringTypeEntry.getInstance());
		
		// The following statement create a new assembly label for the string literal
		// and sets the label in the level of the current function being analyzed
		Label strLabel = new Label();
		currentFunctionLevel.setStringLabel(strExpression.getValue(), strLabel);
		translatedExpAndType.setAssemType(translatingMediator.translateName(strLabel));

		return translatedExpAndType;
	}
	
	public TranslatedExpAndType translateVarExp(Var var)
	{
		TypeEntry varTypeEntry = null;
		AssemType assemTypeVar = null;
		
		if(var instanceof SimpleVar)
		{
			SimpleVar simpleVar = (SimpleVar) var;
			Symbol varName = simpleVar.getName();
			
			VariableEntry variableEntry = null; 
			Entry entr = (Entry)environments.getVariableTable().get(varName);
			if(entr == null)
			{
				errorHandler.addError(var.getPosition(), ErrorHandler.THE_VARIABLE, 
						varName.toString(), ErrorHandler.NOT_DEFINED);
			}
			else
			{
				if(entr instanceof VariableEntry)
				{
					variableEntry = (VariableEntry) entr;
					varTypeEntry = variableEntry.getType();
					
					// Translate to assembly tree 
					int varNum;
					boolean isArgument = variableEntry.isParamVar();
					if(isArgument)
					{
						varNum = variableEntry.getParamNum();
						assemTypeVar = translatingMediator.translateVarExp(varNum, true, 
								ActivationFrame.WORDSIZE, 0);
					}
					else
					{
						Level varLevel = variableEntry.getLevel();    // Level this variable has been declared
																	
						if(variableEntry.isInRegister())
						{
							// The variable is located in a register; check if it is the heap
							if(variableEntry.isOnHeap())
							{
								Temp regTemp = variableEntry.getRegisterTemp();
								if(regTemp == null)
								{
									regTemp = new Temp();
									variableEntry.setRegisterTemp(regTemp);
									assemTypeVar = translatingMediator.translateTemp(regTemp);
								}
								else
								{
									assemTypeVar = translatingMediator.translateTemp(regTemp);
								}
							}
							else
							{
								if(varLevel == currentFunctionLevel)
								{
									// It is the same level as that of the current level
									Temp regTemp = variableEntry.getRegisterTemp();
									assemTypeVar = translatingMediator.translateVarExp(regTemp);	
								}
								else
								{
									// This variable was declared in another function; escape it in THAT function
									// - this will involve removing it from the register and placing it the stack frame
									varNum = varLevel.getActivationFrame().getCurrentOffSet(); 
									varLevel.getActivationFrame().setCurrentOffSet(++varNum);
									variableEntry = new VariableEntry(variableEntry.getType(), false, varNum, varLevel);
									
									// Refer this from the stack frame
									int staticLinkLevelNum = 0;
									if(varLevel != currentFunctionLevel)
									{
										// The variable being referred to is not declared in this function
										staticLinkLevelNum = getVarLevelNum(varLevel);
									}
									assemTypeVar = translatingMediator.translateVarExp(varNum, isArgument, 
											ActivationFrame.WORDSIZE, staticLinkLevelNum);
								}
							}
						}
						else
						{
							// The variable is located on the stack frame
							varNum = variableEntry.getLocalVariableNum();

							int staticLinkLevelNum = 0;
							if(varLevel != currentFunctionLevel)
							{
								// The variable being referred to is not declared in this function
								staticLinkLevelNum = getVarLevelNum(varLevel);
							}
							assemTypeVar = translatingMediator.translateVarExp(varNum, isArgument, 
									ActivationFrame.WORDSIZE, staticLinkLevelNum);
						}
					}
				}
				else
				{
					errorHandler.addError(var.getPosition(), ErrorHandler.THE_VARIABLE, 
							varName.toString(), ErrorHandler.NOT_DEFINED);
				}
			}
			
		}
		else if(var instanceof SubscriptVar)
		{
			SubscriptVar subscriptVar = (SubscriptVar) var;
			Var baseVar = subscriptVar.getBaseVar();
			Exp indexExp = subscriptVar.getIndex();
			
			if(translateExpression(indexExp).getTypeEntry() != IntTypeEntry.getInstance())
			{
				errorHandler.addError(var.getPosition(), ErrorHandler.ARRAY_INDEX_NOT_INT);
			}
			
			TypeEntry tpEntr = translateVarExp(baseVar).getTypeEntry();
			if(tpEntr instanceof ArrayTypeEntry)
			{
				ArrayTypeEntry arrTypeEntry = (ArrayTypeEntry) tpEntr;
				varTypeEntry = arrTypeEntry.getBaseTypeEntry();
			}
			else
			{
				// TODO - Handle this Error!!!
			}
		}
		else if(var instanceof FieldVar)
		{
			FieldVar fieldVar = (FieldVar) var;
			Var baseVar = fieldVar.getBaseVar();
			Symbol fldSym = fieldVar.getFieldSymbol();
			
			TranslatedExpAndType baseExpAndType = translateVarExp(baseVar);
			TypeEntry baseTpEntry =  baseExpAndType.getTypeEntry();
			AssemType assemTypeBase = baseExpAndType.getAssemType();
			
			if(baseTpEntry instanceof RecordTypeEntry)
			{
				RecordTypeEntry baseRecTpEntry = (RecordTypeEntry) baseTpEntry;
				boolean foundField = false;
				int fieldOffSet = 0;
				for(RecordTypeEntry brte = baseRecTpEntry; brte != null; brte = brte.getNext())
				{
					fieldOffSet++;
					Symbol foundFldSym = brte.getFieldName();
					if(foundFldSym == fldSym)
					{
						foundField = true;
						varTypeEntry = brte.getFieldType();
						break;
					}
				}
				if(!foundField)
				{
					errorHandler.addError(var.getPosition(), ErrorHandler.THE_FIELD_NAME, 
							fldSym.toString(), ErrorHandler.NOT_DEFINED);
				}
				else
				{
					assemTypeVar = translatingMediator.translateFieldVarExp(assemTypeBase, --fieldOffSet);
				}
			}
			else if(baseTpEntry instanceof ArrayTypeEntry)
			{
				ArrayTypeEntry ate = (ArrayTypeEntry) baseTpEntry;
				varTypeEntry = ate.getBaseTypeEntry();
			}	
		}
		
		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		translatedExpAndType.setEntry(varTypeEntry);
		translatedExpAndType.setAssemType(assemTypeVar);
		return translatedExpAndType;
	}
	
	public TranslatedExpAndType translateWhileExp(WhileExp whileExpression)
	{
		loopBeingAnalyzed = true;
		Exp whileTest = whileExpression.getTest();
		Exp whileBody = whileExpression.getBody();
		
		TranslatedExpAndType whileTestExpAndType = translateExpression(whileTest);
		TranslatedExpAndType whileBodyExpAndType = translateExpression(whileBody);
		
		if(!(whileTestExpAndType.getTypeEntry() instanceof IntTypeEntry))
		{
			semanticAnalyzerError = true;
			errorHandler.addError(whileTest.getPosition(), ErrorHandler.WHILE_TEST_NOT_INT);
		}
		
		if(!(translateExpression(whileBody).getTypeEntry() instanceof VoidTypeEntry))
		{
			semanticAnalyzerError = true;
			errorHandler.addError(whileTest.getPosition(), ErrorHandler.WHILE_BODY_NOT_VOID);
		}
		
		TranslatedExpAndType translatedExpAndType = new TranslatedExpAndType();
		if(!semanticAnalyzerError)
		{
			// No error during semantic processing - translate it to the intermediate representation
			AssemType whileTestAssemType = whileTestExpAndType.getAssemType();
			AssemType whileBodyAssemType = whileBodyExpAndType.getAssemType();
			
			Label whileTestLabel = new Label();
			Label endWhileLabel = new Label();
			
			loopLabelStack.push(endWhileLabel);  // Push the endwhile label so it can be used by
												 // the break statement (if required)
			
			AssemType assemSeq = translatingMediator.translateWhileExp(whileTestAssemType, 
							whileBodyAssemType, whileTestLabel, endWhileLabel);
			translatedExpAndType.setAssemType(assemSeq);
		}
		
		translatedExpAndType.setEntry(VoidTypeEntry.getInstance()); // While expression does not evaluate to any value
		
		loopBeingAnalyzed = false;
		return translatedExpAndType;
	}
	
	/*
	 * A non-null value is returned only if the input parameter is a variable declaration
	 */
	
	public AssemType translateDec(Dec dec)
	{
		if(dec instanceof FunctionDec)
		{
			FunctionDec functionDec = (FunctionDec) dec;
			translateFunctionDec(functionDec);
			return null;
		}
		if(dec instanceof VarDec)
		{
			VarDec varDec = (VarDec) dec;
			return translateVarDec(varDec);		
		}
		else if(dec instanceof TypeDec)
		{
			TypeDec typeDec = (TypeDec) dec;
			Symbol typeName = typeDec.getName();
			Ty typeCategory = typeDec.getTypeCategory();
			
			TypeEntry typeEntry = translateTypeCategory(typeCategory);
			
			// Make the entry in the type environment
			environments.getTypeTable().put(typeName, typeEntry);	
			return null;
		}
		return null;
	}
	
	public void translateFunctionDec(FunctionDec functionDec)
	{
		Symbol funcName = functionDec.getName();
		FieldList params = functionDec.getParams();
		NameTy returnType = functionDec.getResult();
		Exp body = functionDec.getBody();
		
		TypeEntry retTypeEntry = null;
		// Type checking of return type
		if(returnType != null)
		{
			Symbol retTypeSymbol = returnType.getName();
			retTypeEntry = (TypeEntry) environments.getTypeTable().get(retTypeSymbol);
			if(retTypeEntry == null)
			{
				errorHandler.addError(functionDec.getPosition(), ErrorHandler.THE_TYPE, 
						retTypeSymbol.toString(), ErrorHandler.NOT_DEFINED);
			}
		}
		else
		{
			// This is a void return type
			retTypeEntry = VoidTypeEntry.getInstance();
		}
		
		// Type checking of the param list
		RecordTypeEntry formals = translateFieldList(params);
		
		// Make the function entry
		currentFunctionLevel = new Level(currentFunctionLevel, funcName);
		// Add this level to the pool of function entries
		functionLevels.addElement(currentFunctionLevel);
	
		FunctionEntry funcEntry = new FunctionEntry(formals, retTypeEntry, currentFunctionLevel);
		environments.getVariableTable().put(funcName, funcEntry);
		
		// Create a new scope for the variable/function environment and analyze
		environments.getVariableTable().beginScope();
		int paramCount = 0;
		for(FieldList prm = params; prm != null; prm = prm.getNextFieldList())
		{
			Symbol varName = prm.getName();			
			TypeEntry entr = (TypeEntry)environments.getTypeTable().get(prm.getTypeSymbol());
			//ActivationFrame currAf = currentFunctionLevel.getActivationFrame();
			//int currVarOffSet = currAf.getCurrentOffSet();
			//currAf.setCurrentOffSet(++currVarOffSet);
			VariableEntry varEntry = new VariableEntry(entr, true, ++paramCount, currentFunctionLevel);
			environments.getVariableTable().put(varName, varEntry);
		}
		
		currentFunctionLevel.setNumIncomingParams(paramCount);
		
		// Check the body expression and set the translated type into the level object
		TranslatedExpAndType translatedExpAndType = translateExpression(body);
		
		AssemType assemBody = translatedExpAndType.getAssemType();
		assemBody = translatingMediator.translateReturnStm(assemBody);
		
		currentFunctionLevel.setFunctionBody(assemBody);

		// If the return type declared and that found in the body are not the same,
		// display an error message
		
		if(retTypeEntry != (TypeEntry) translatedExpAndType.getTypeEntry())
		{
			errorHandler.addError(functionDec.getPosition(), ErrorHandler.FUNC_RETTYPE_NOT_COMPATIBLE);
		}
		
		// End the scope
		environments.getVariableTable().endScope();	
		
		// The function has ended; now retrieve the earlier level of the function
		currentFunctionLevel = currentFunctionLevel.getParentLevel();
	}
	
	public AssemType translateVarDec(VarDec varDec)
	{
		Symbol varName = varDec.getName();
		NameTy nameTy = varDec.getNameTy();
		Exp initExpression = varDec.getInitExpression();
		
		TranslatedExpAndType translatedExpAndTypeInitExpr = translateExpression(initExpression);
		TypeEntry exprType = translatedExpAndTypeInitExpr.getTypeEntry();
		AssemType exprAssemType = translatedExpAndTypeInitExpr.getAssemType();
		
		if(exprType == null)
		{
			errorHandler.addError(varDec.getPosition(), ErrorHandler.INIT_EXPR_TYPE_NOT_FOUND);
		}
		
		// Check if the types are ok first
		TypeEntry typeEntry = null;
		if(nameTy != null)
		{
			// The type has been specified; check that the type and the expression match
			Symbol typeName = nameTy.getName();
			typeEntry = (TypeEntry) environments.getTypeTable().get(typeName);
			if(typeEntry == null)
			{
				errorHandler.addError(nameTy.getPosition(), ErrorHandler.THE_TYPE, 
						typeName.toString(), ErrorHandler.NOT_DEFINED);
			}
			else
			{	
				typeEntry = translateNameTy(typeEntry);
				if(typeEntry != exprType)
				{
					// The two types are not the same; check if the lhs is a record type and the rhs 
					// is a nil type
					if(!(typeEntry instanceof RecordTypeEntry && exprType instanceof NilTypeEntry))
						errorHandler.addError(varDec.getPosition(), ErrorHandler.TYPE_MISMATCH_VARDEC);
				}
			}
		}
		else
			typeEntry = exprType;

		// Check if there is already a function or variable defined in the current scope with the same name
		
		if(environments.getVariableTable().keyInCurrentScope(varName))
		{
			errorHandler.addError(varDec.getPosition(), ErrorHandler.THE_VARIABLE, 
					varName.toString(), ErrorHandler.ALREADY_DEFINED);
		}
		
		// Make new entry	
		//TODO - implement the following and uncomment to remove hard-coding of escaes
		//Boolean escBool = (Boolean) environments.getEscapeTable().get(varName);
		//boolean escapes = escBool.booleanValue();
		boolean escapes = false;
		AssemType varDecAssem = null;
		if(!escapes)
		{
			// Does not escape; can be kept in the register; but only if it is not a record or an array type
			if(typeEntry instanceof RecordTypeEntry)
			{
				VariableEntry varEntry = new VariableEntry(typeEntry, currentFunctionLevel);
				environments.getVariableTable().put(varName, varEntry);	
				varDecAssem = null;
			}
			else
			{
				// Can be kept in the register
				Temp newVarTemp = new Temp();
				VariableEntry varEntry = new VariableEntry(typeEntry, newVarTemp, currentFunctionLevel);
				environments.getVariableTable().put(varName, varEntry);	
				varDecAssem = translatingMediator.translateVarDec(newVarTemp, exprAssemType);
			}
		}
		else
		{
			// Must be kept on the stack frame
			ActivationFrame currAf = currentFunctionLevel.getActivationFrame();
			int currVarOffSet = currAf.getCurrentOffSet();
			currAf.setCurrentOffSet(++currVarOffSet);
			
			VariableEntry varEntry = new VariableEntry(typeEntry, false, currVarOffSet, currentFunctionLevel);
			environments.getVariableTable().put(varName, varEntry);
			
			varDecAssem = translatingMediator.translateVarDec(currVarOffSet, ActivationFrame.WORDSIZE, exprAssemType);	
		}
		return varDecAssem;
	}
	
	public TypeEntry translateTypeCategory(Ty typeCategory)
	{		
		TypeEntry retTypeEntry = null;
		if(typeCategory instanceof NameTy || typeCategory instanceof ArrayTy)
		{
			Symbol sym = null;
			TypeEntry typeEntryOfParam = null;
			if(typeCategory instanceof NameTy)
			{
				NameTy nameTy = (NameTy) typeCategory;
				sym = nameTy.getName();	
				typeEntryOfParam = (TypeEntry) environments.getTypeTable().get(sym);
				if(typeEntryOfParam == null)
				{
					errorHandler.addError(typeCategory.getPosition(), ErrorHandler.THE_TYPE, 
							sym.toString(), ErrorHandler.NOT_DEFINED);
				}
				
				if(typeEntryOfParam instanceof IntTypeEntry || typeEntryOfParam instanceof StringTypeEntry)
				{
					//If this is an int or a string, no need to create a new nametype entry
					return typeEntryOfParam;
				}
				
				// This is a name type; create a new name type entry and return it
				NameTypeEntry nameTypeEntry = new NameTypeEntry(sym);
				nameTypeEntry.bind(typeEntryOfParam);
				retTypeEntry = nameTypeEntry;
			}
			else
			{
				// This is an array type
				ArrayTy arrayTy = (ArrayTy) typeCategory;
				sym = arrayTy.getBaseTypeSymbol();
				typeEntryOfParam = (TypeEntry) environments.getTypeTable().get(sym);
				if(typeEntryOfParam == null)
				{
					errorHandler.addError(typeCategory.getPosition(), ErrorHandler.THE_TYPE, 
							sym.toString(), ErrorHandler.NOT_DEFINED);
				}
				ArrayTypeEntry arrayTypeEntry = new ArrayTypeEntry(typeEntryOfParam);
				retTypeEntry = arrayTypeEntry;
			}
		}
		else if(typeCategory instanceof RecordTy)
		{
			RecordTy recordTy = (RecordTy) typeCategory;
			FieldList fieldList = recordTy.getFieldList();		
			retTypeEntry = translateFieldList(fieldList);
			
		}
		return retTypeEntry;
	}
	
	private RecordTypeEntry translateFieldList(FieldList fieldList)
	{
		RecordTypeEntry recordTypeEntry = null;
		RecordTypeEntry nextRecordTypeEntry = null;
		while(fieldList != null)
		{
			Symbol fieldName = fieldList.getName();
			Symbol typeSymbol = fieldList.getTypeSymbol();
			
			TypeEntry fieldTypeEntry = (TypeEntry) environments.getTypeTable().get(typeSymbol);
			if(fieldTypeEntry == null)
			{
				errorHandler.addError(fieldList.getPosition(), ErrorHandler.THE_TYPE, 
						fieldName.toString(), ErrorHandler.NOT_DEFINED);
			}
			
			if(recordTypeEntry == null)
			{
				// Entered loop for the first time
				recordTypeEntry = new RecordTypeEntry(fieldName, fieldTypeEntry, null);
				nextRecordTypeEntry = recordTypeEntry;
			}
			else
			{
				// Create a new RecordTypeEntry and add it to the tail of the previous entry
				RecordTypeEntry tempRecordTypeEntry = new RecordTypeEntry(fieldName, fieldTypeEntry, null);
				nextRecordTypeEntry.setTail(tempRecordTypeEntry);
				nextRecordTypeEntry = tempRecordTypeEntry;
			}
			
			fieldList = fieldList.getNextFieldList();
		}
		
		return recordTypeEntry;
	}
	
	public TypeEntry translateNameTy(TypeEntry typeEntry)
	{
		if(typeEntry instanceof NameTypeEntry)
		{
			NameTypeEntry teNext = (NameTypeEntry) typeEntry;
			return translateNameTy(teNext.getBinding());
		}
		return typeEntry;
			
	}
	
	private int getVarLevelNum(Level varLevel)
	{
		int sum = 0;
		int incr = 32;
		Level tempLevel = currentFunctionLevel;
		while(tempLevel != varLevel)
		{
			tempLevel = tempLevel.getParentLevel();
			sum += incr;
		}
		
		return sum;
	}
}
